require 'ParserUtils'
require 'Messages'
require 'pat'
require 'Forwardable'


OFFSET_CONST = 5
OFFSET_PIR   = 2
NAMES_PREFIX = 'comp_'

class String
  def append_with_spaces(sp, str)
    sp.times {self << " "}
    self << str
    self
  end
end

class SyntaxObject
  extend Forwardable
  
  @@number = 10
  def is_left_value?
    return false
  end
  
  def inference_type(*a)
    #types are :string > :float > :int
    t = a.max {|a,b| a.to_s.length <=> b.to_s.length}
    if [a].include?(:float)
      @_type = :float
    elsif [a].include?(:int)
      @_type = :int
    end
  end
  
  def gen_number
    @number = @@number = get_number.next
  end
  
  def get_number
    @number = @@number if @number.nil?
    @number
  end
  
  def sym_table_to_parrot(table, offset)
    str = ""
    table.each_pair do |k, v|
      #generate local named variables for program
      str.append_with_spaces(offset, ".local #{type v.syn_type} #{NAMES_PREFIX + k}\n") if !(v.syn_type == :def)
    end
    str += "\n" if str != ""
    str
  end
  
  def type(sym)
    case sym
    when :array
      "pmc"
    when :undef
      "pmc"
    else
      sym.to_s
    end
  end
  
  def register_type(e)
    return "P" if e.nil?
    e = e.kind_of?(Symbol) ? e : e.syn_type 
    case e
    when :int
      "I"
    when :float
      "N"
    when :string
      "S"
    else
      "P"
    end
  end
  
  def gen_register(params)
    syn_obj = params[:object]
    l = if syn_obj.kind_of?(SynVar)
      register_type params[:sym_table][syn_obj.syn_obj]
    else
      register_type syn_obj
    end
    "$#{l}#{gen_number}"
  end
end

class Expr < SyntaxObject
  
end

class UnaryOp < Expr
  attr_reader :arg, :op
  def initialize(op,arg)
    @op = op
    @arg = arg
  end
  
  def syn_type
    @arg.syn_type
  end
  
  def to_parenth_notation
    "(#{@op} #{@arg.to_parenth_notation})"
  end
  
  def get_childs
    return [@arg]
  end
  
  def to_s
    "#{@op}"
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "#{@op} - #{@arg.to_tree_view(offset + OFFSET_CONST)}\n"
    return str
  end
  
  def generate_to_parrot
    "#{@op}#{@arg.generate_to_parrot}"
  end
end

class BinaryOp < Expr
  attr_reader :op, :left, :right
  attr_writer :left
  def initialize(op,left,right)
    @op = op
    @left = left
    @right = right
  end
  
  def syn_type
    return @_type unless @_type.nil?
    if ['<', '>', '==', '<=', '>='].include?(@op)
      @_type = :int
    else
      t = inference_type @left.syn_type, @right.syn_type
      if ['+','-','*'].include?(@op)
        if [@left.syn_type, @right.syn_type].include?(:float)
          @_type = :float
        elsif [@left.syn_type, @right.syn_type].include?(:int) || t == :undef
          @_type = :int
        end
      end
    end
  end
  
  def to_parenth_notation
    left = @left.nil? && 'nil' || @left.to_parenth_notation
    right = @right.nil? && 'nil' || @right.to_parenth_notation
    "(#{@op} #{left} #{right})"
  end
  
  def to_tree_view(offset)
    str = String.new()
    of = String.new(); offset.times { of += " "}
    str += "#{@left.to_tree_view(offset + OFFSET_CONST)}\n"
    str += of + "  / \n"
    str += of + "#{@op}\n" 
    str += of + "  \\ \n#{@right.to_tree_view(offset + OFFSET_CONST)}"
  end
  
  def to_s
    "#{@op}"
  end
  
  def eval
    le = @left.eval()
    re = @right.eval()
    return EvalInfo.new(false) if (!le.evalable && !re.evalable)
    ret = case @op
    when '+' 
      le.value + re.value
    when '-' 
      le.value - re.value
    when '*' 
      le.value * re.value
    when '/' 
      le.value / re.value
    when '=' 
      re.value
    end
    return EvalInfo.new(true,ret)
  end
  
  def get_childs
    return [@left, @right]
  end
  
  def generate_to_parrot(params)
    return if @right.kind_of?(FuncDefination)
    str = ""
    is_statement = params[:is_statement] || false
    params[:is_statement] = false
    offset = params[:offset]
    str.append_with_spaces(offset, "\##{self.to_parenth_notation}\n")
    if @op == '='
      str += @right.generate_to_parrot(params)
      s_table = params[:sym_table]
      r = @right.kind_of?(SynVar) ? s_table[@right] : @right
      reg1 = "$#{register_type r}1"

      if @left.kind_of?(SynArraySelection)
        str += @left.generate_to_parrot_assign(params)
        str.append_with_spaces(offset, "restore $I9\n")
        str.append_with_spaces(offset, "restore #{reg1}\n")
        str.append_with_spaces(offset, "#{NAMES_PREFIX + @left.variable.to_s}[$I9] = #{reg1}\n")
        
      else
        l = NAMES_PREFIX + @left.to_s
        str.append_with_spaces(offset, "restore #{reg1}\n")
        str.append_with_spaces(offset, "#{l} = #{reg1}\n")
      end
      # str.append_with_spaces(offset, "save #{NAMES_PREFIX + @left.to_s}\n") if !is_statement
      str
    else
      str += @right.generate_to_parrot(params)
      str += @left.generate_to_parrot(params)
      
      s_table = params[:sym_table]
      r = @right.kind_of?(SynVar) ? s_table[@right] : @right
      l = @left.kind_of?(SynVar) ? s_table[@left] : @left
      
      @_type = if ['<', '>', '==', '<=', '>='].include?(@op)
        :int
      else
        inference_type r.syn_type, l.syn_type
      end
      reg1 = "$#{register_type r}1"
      reg2 = "$#{register_type l}2"
      reg3 = "$#{register_type @_type}3"
      

      inference_type @left.syn_type, @right.syn_type
      str.append_with_spaces(offset, "restore #{reg2}\n")
      str.append_with_spaces(offset, "restore #{reg1}\n")
      str.append_with_spaces(offset, "#{reg3} = #{reg2} #{@op} #{reg1}\n")
      str.append_with_spaces(offset, "save #{reg3}\n")
      str += "\n"
    end
      
    # if right.kind_of?(SynArrayConst) && @op == '=' && left.kind_of?(SynVar)
    #   r += right.generate_to_parrot({:name => left}.merge(params))
    # else
    #   r = @right.generate_to_parrot(:offset => 0, :sym_table => params[:sym_table])
    # end
  end
end

class SynVar < SyntaxObject
  attr_reader :name
  
  def initialize(name)
    @name = name
  end
  
  def to_parenth_notation
    "#{@name}"
  end

  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "#{@name}\n"
    return str
  end
  
  def to_s
    "#{@name}"
  end
  
  def eval
    return EvalInfo.new(false)
  end
  
  def is_left_value?
    return true
  end
  
  def syn_type
    :undef
  end
  
  def syn_name
    @name
  end
  
  def to_syn_object(table)
    SynObjectVar.new(@name, table)
  end
  
  def to_syn_array
    SynArrayVar.new(@name)
  end
  
  def generate_to_parrot(params)
    if params[:sym_table]
      "".append_with_spaces(params[:offset], "save #{NAMES_PREFIX + @name}\n")
    else
      throw :maybe_error
    end
    # return @name
  end
end

class SynArrayVar < SynVar
  attr_reader :name, :sym_table
  
  def initialize(name)
    @name = name
    @sym_table = Pat.new(@name)
  end

  def syn_type
    :array
  end
  
  def to_syn_object(table)
    throw :unimplemented
  end
end

class SynObjectVar < SyntaxObject
  attr_reader :name, :sym_table
  
  def initialize(name, table)
    @name = name.kind_of?(SynVar) ? name.name : name
    @sym_table = table
  end
  
  def set_cell(cell)
    if @sym_table[cell].nil?# && !cell.kind_of?(SynFunctionCall)
      @sym_table[cell] = SymTableRecord.new(cell.syn_type, cell)
    end
    return self
  end
  
  def has_cell?(cell)
    return @sym_table[cell].nil? || true
  end
  
  def to_parenth_notation
    "#{@name}"
  end

  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "#{@name}\n"
    return str
  end
  
  def to_s
    "#{@name}"
  end
  
  def is_left_value?
    return true
  end
  
  def syn_type
    :object
  end
  
  def syn_name
    @name
  end
  
  def to_syn_object(t)
    self
  end
end

class SynConst < SyntaxObject
  def initialize(value)
    @value = value
  end
  
  def syn_name
    @value
  end
  
  def to_parenth_notation
    "#{@value}"
  end
  
  def syn_type
    @value.kind_of?(Integer) ? :int : :float
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "#{@value}\n"
    return str
  end
  
  def to_s
    "#{@value}"
  end
  
  def eval
    return EvalInfo.new(true, @value)
  end
  
  def generate_to_parrot(params)
    reg = "$#{register_type(self)}1"
    str = "".append_with_spaces(params[:offset], "#{reg} = #{@value.to_s}\n")
    str.append_with_spaces(params[:offset], "save #{reg}\n")
  end
end

class SynStrConst < SynConst
  def generate_to_parrot(params)
    str = "".append_with_spaces(params[:offset], "$S1 = \"#{@value.to_s}\"\n")
    str.append_with_spaces(params[:offset], "save $S1\n")
  end
  
  def syn_type
    :string
  end
end

class SynArrayConst < SyntaxObject
  attr_reader :value
  
  def initialize(array)
    @value = array
  end
  
  def to_parenth_notation
    "[#{@value.map {|e| e.to_parenth_notation}.join(', ')}]"
  end
  
  def syn_type
    :array
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "#{@value.join(',')}\n"
    return str
  end
  
  def to_s
    "[#{@value.join(' ')}]"
  end
  
  def eval
    return EvalInfo.new(false,0)
  end
  
  def generate_to_parrot(params)
    name = "$P1"
    offset = params[:offset]
    str = "".append_with_spaces(offset, "#{name} = new .Array\n")
    offset = offset + OFFSET_PIR
    
    str.append_with_spaces(offset, "#{name} = #{@value.length}\n")
    
    @value.each_index do |i|
      str.append_with_spaces(offset, "#{name}[#{i}] = #{@value[i]}\n")
    end
    
    offset = offset - OFFSET_PIR
    str.append_with_spaces(offset, "save #{name}\n")
  end
end

class SynBlock < SyntaxObject
  def_delegators :@stmts, :[], :each
  
  def initialize(stmts)
    @stmts = stmts
  end
  
  def to_parenth_notation
    return '[' + @stmts.inject('') { |str, st| str << st.to_parenth_notation << ', ' } + ']'
  end
  
  def to_tree_view(offset)
    ret = String.new()
    @stmts.each {|s| ret += "#{s.to_tree_view(offset)}\n"}
    return ret
  end
  
  def to_s
    "Block"
  end
  
  def generate_to_parrot(params)
    table = params[:sym_table]
    offset = params[:offset]
    str = ""
    @stmts.each {|statement|
      params[:is_statement] = true
      str += "#{statement.generate_to_parrot(params)}\n"
    }
    str
  end
  
end

class SynProgram < SynBlock
  def initialize(stmts, table)
    @stmts, @sym_table = stmts, table
    @block = SynBlock.new(@stmts)
  end
  
  def to_s
    "Program"
  end
  
  def generate_to_parrot
    str = ""
    offset = 0
    @sym_table.get_definations.each { |arr|
      (name, defination) = arr
      str += defination.object.generate_to_parrot(:offset => 0, :name =>name, :sym_table => @sym_table)
    }
    
    str += "\n.sub application :main\n"
    offset = offset + OFFSET_PIR
    str += sym_table_to_parrot(@sym_table, offset)
    str += @block.generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    # @stmts.each do |stmt|
    #   res = stmt.generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    #   str += "#{res}\n"
    # end
    str += ".end\n"
    return str
  end
end

class SynFor < SyntaxObject
  attr_reader :sym_table
  
  def initialize(sym_table, initiator, condition, transformer, block)
    @init, @cond, @trans, @block = initiator, condition, transformer, block
    @sym_table = sym_table
  end
  
  def to_parenth_notation
    return "(for #{@block.to_parenth_notation})"
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "for : (#{@init.to_parenth_notation}, #{@cond.to_parenth_notation}, #{@trans.to_parenth_notation})\n"
    str += "#{@block.to_tree_view(offset + OFFSET_CONST)}"
    return str
  end
  
  def to_s
    "For"
  end
  
  def generate_to_parrot(params)
    gen_number
    offset = params[:offset]
    str = ""
    str += sym_table_to_parrot(@sym_table, offset)
    str += @init.generate_to_parrot(:sym_table => @sym_table, :offset => params[:offset]) + "\n"
    str.append_with_spaces(offset - OFFSET_CONST, "FOR#{get_number}:\n")
    
    str += @cond.generate_to_parrot(params.merge(:sym_table => @sym_table))
    str.append_with_spaces(offset, "restore $I1\n")
    str.append_with_spaces(offset, "unless $I1 goto FOR#{get_number}_END\n")
    str += SynBlock.new(@block).generate_to_parrot(:offset => offset + OFFSET_CONST, :sym_table => @sym_table)
    str += @trans.generate_to_parrot(params.merge(:sym_table => @sym_table)) + "\n"
    str.append_with_spaces(offset, "goto FOR#{get_number}\n")
    str.append_with_spaces(offset - OFFSET_CONST, "FOR#{get_number}_END:\n")
  end
end

class SynIf < SyntaxObject
  attr_reader :sym_table
  
  def initialize(sym_table, condition,  consequence, alternative=nil)
    @cond, @cons, @alt, @sym_table = condition, consequence, alternative, sym_table
  end
  
  def to_parenth_notation
    if @alt.nil?
      return "(if [#{@cond.to_parenth_notation}] #{@cons.to_parenth_notation})"
    else
      return "(if [#{@cond.to_parenth_notation}] #{@cons.to_parenth_notation}) else [#{@alt.to_parenth_notation}]"
    end
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "for : (#{@init.to_parenth_notation}, #{@cond.to_parenth_notation}, #{@trans.to_parenth_notation})\n"
    str += "#{@block.to_tree_view(offset + OFFSET_CONST)}"
    return str
  end
  
  def to_s
    "If"
  end
  
  def generate_to_parrot(params)
    offset = params[:offset]
    str = ""
    gen_number
    str += sym_table_to_parrot(@sym_table, offset)
    str += @cond.generate_to_parrot(:offset => offset + OFFSET_CONST, :sym_table => params[:sym_table])
    str.append_with_spaces(offset, "restore $I1\n")
    if (@alt.nil?)
      str.append_with_spaces(offset, "unless $I1 goto IF#{get_number}_END\n")
      str += SynBlock.new(@cons).generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    else
      str.append_with_spaces(offset, "unless $I1 goto IF#{get_number}\n")
      str += SynBlock.new(@cons).generate_to_parrot(:offset => offset, :sym_table => @sym_table)
      str.append_with_spaces(offset, "goto IF#{get_number}_END\n")
      
      str.append_with_spaces(offset - OFFSET_CONST, "IF#{get_number}:\n")
      str += SynBlock.new(@alt).generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    end
    str.append_with_spaces(offset, "IF#{get_number}_END:\n")
    str
  end
end

class SynWhile < SyntaxObject
  attr_reader :sym_table, :block
  
  def initialize(sym_table, condition, block)
    @cond, @block, @sym_table = condition, block, sym_table
  end
  
  def to_parenth_notation
    return "(while [#{@cond.to_parenth_notation}] #{@block.to_parenth_notation})"
  end
  
  def to_tree_view(offset)
    str = String.new();
    offset.times { str += " ";}
    str += "while : (#{@cond.to_parenth_notation})\n"
    str += "#{@block.to_tree_view(offset + OFFSET_CONST)}"
    return str
  end
  
  def to_s
    "For"
  end
  
  def generate_to_parrot(params)
    offset = params[:offset] + OFFSET_PIR
    gen_number
    str = "".append_with_spaces(offset - OFFSET_PIR, "WHILE#{get_number}:\n")
    str += sym_table_to_parrot(@sym_table, offset)
    str += @cond.generate_to_parrot(params.merge(:offset => offset))
    str.append_with_spaces(offset, "restore $I1\n")
    str.append_with_spaces(offset, "unless $I1 goto WHILE#{get_number}_END\n")
    
    str += @block.generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    
    str.append_with_spaces(offset, "goto WHILE#{get_number}\n")
    str.append_with_spaces(offset - OFFSET_PIR, "WHILE#{get_number}_END:\n")
  end
end

class FuncDefination < SyntaxObject
  attr_reader :sym_table
  
  def initialize(sym_table, args, body)
    @args, @body, @sym_table = args, body, sym_table
  end
  
  def syn_type
    :def
  end
  
  def to_parenth_notation
    return "{defination (#{@args.join(',').to_s}) #{@body.to_parenth_notation}}"
  end
  
  def generate_to_parrot(params)
    name    = params[:name]
    offset  = params[:offset]
    
    str = ".sub #{name}\n"
    offset = offset + OFFSET_PIR
    @args.each do |a|
      type = type @sym_table[a].syn_type
      str.append_with_spaces(offset, ".param #{type} #{NAMES_PREFIX + a.syn_name}\n")
    end
    
    args = @args.map {|a| a.syn_name}
    @sym_table.each_pair do |k, v|
      #generate local named variables for program
      next if  args.include?(k)
      str.append_with_spaces(offset, ".local #{v.syn_type.to_s} #{NAMES_PREFIX + k}\n") if !(v.syn_type == :def)
    end
    str += "\n"
    str += SynBlock.new(@body).generate_to_parrot(:offset => offset, :sym_table => @sym_table)
    str += ".end\n"
    str
  end
end

class SynFunctionCall < SyntaxObject
  def initialize(name, args)
    @name = name
    @args = args
  end
  
  def syn_type
    :undef
  end
  
  def syn_name
    @name
  end
  
  def is_left_value?
    return false
  end
  
  def to_parenth_notation
    return "(#{@name} #{@args.join(' ').to_s})"
  end
  
  def to_s
    return "(#{@name} #{@args.join(' ').to_s})"
  end
  
  def generate_to_parrot(params)
    offset = params[:offset]
    str = ""
    str.append_with_spaces(params[:offset], '#' + self.to_parenth_notation + "\n")
    ar = []
    @args.each_index do |e|
      i = gen_number
       v = @args[e]
        l = if v.kind_of?(SynVar)
          register_type params[:sym_table][v.syn_name]
        else
          register_type v
        end
      reg = "$#{l}#{i}"
      str.append_with_spaces(params[:offset], v.generate_to_parrot(params))
      # str.append_with_spaces(params[:offset], "#{reg} = #{@args[e].generate_to_parrot( {:offset => 0}.merge(params) )}\n")
      str.append_with_spaces(params[:offset], "restore #{reg}\n")
      ar << reg
    end
    
    str.append_with_spaces(offset, ".const .Sub sub_name = '#{@name}'\n")
    str.append_with_spaces(offset, ".pcc_begin\n")
    offset = offset + OFFSET_PIR
    @args.each_index {|i| 
      str.append_with_spaces(offset, ".arg #{ar[i]}\n")
    }
    str.append_with_spaces(offset, ".pcc_call sub_name\n")
    str.append_with_spaces(offset, ".local pmc call_results\n")
    str.append_with_spaces(offset, ".result call_results\n")
    
    offset = offset - OFFSET_PIR
    str.append_with_spaces(offset, ".pcc_end\n")
    str.append_with_spaces(offset, "save call_results\n")
  end
end

class SynParrotCall < SynFunctionCall
  def generate_to_parrot(params)
    str = "".append_with_spaces(params[:offset], "##{to_parenth_notation}\n")
    ar = []
    @args.each_index do |e|
      i = gen_number
      v = @args[e]
      l = if v.kind_of?(SynVar)
        register_type params[:sym_table][v.syn_name]
      else
        register_type v
      end
      reg = "$#{l}#{i}"
      # str.append_with_spaces(params[:offset], "#{reg} = #{v.generate_to_parrot(params)}")
      str += v.generate_to_parrot(params)
      str.append_with_spaces(params[:offset], "restore #{reg}\n")
      # str.append_with_spaces(params[:offset], "#{reg} = #{@args[e].generate_to_parrot( {:offset => 0}.merge(params) )}\n")
      ar << reg
    end
    
    str.append_with_spaces(params[:offset], "#{@name} #{ar.join(', ')}")
    str
  end
  
  def self.op_codes
    ['print']
  end
end

class SynArraySelection < SyntaxObject
  def_delegators :@variable, :syn_name
  attr_reader :index
  attr_accessor :variable
  
  def initialize(variable, index)
    @variable = variable
    @index = index
  end
  
  def to_parenth_notation
    return "[#{@variable.to_parenth_notation} #{@index.to_parenth_notation}]"
  end
  
  def to_s
    "[#{@variable.to_parenth_notation} #{@index.to_parenth_notation}]"
  end
  
  def sym_table
    @sym_table ||= Pat.new(:name => to_s)
  end
  
  def to_syn_object
    SynObjectVar.new(self)
  end
  
  def set_cell(cell)
    sym_table[cell] = SymTableRecord.new(:object, cell)
  end
  
  def is_left_value?
    return true
  end
  
  def syn_type
    if @index.kind_of?(Integer)
      @variable.sym_table[@index].syn_type
    else
      :undef
    end
  end
  
  def generate_to_parrot(params)
    str = ""
    offset = params[:offset]
    index = if @index.kind_of?(SynVar)
      NAMES_PREFIX + @index.syn_name
    else
      str += @index.generate_to_parrot(params)
      str.append_with_spaces(offset, "restore $I1\n")
      "$I1"
    end
    
    r = register_type self
    reg = "$#{r}2"
    str.append_with_spaces(offset, "#{reg} = #{NAMES_PREFIX + @variable.syn_name}[#{index}]\n")
    str.append_with_spaces(offset, "save #{reg}\n")
  end
  
  def generate_to_parrot_assign(params)
    str = ""
    if @index.kind_of?(SynVar)
      str.append_with_spaces(params[:offset], "save #{NAMES_PREFIX + @index.syn_name}\n")
    else
      str += @index.generate_to_parrot(params)
    end
  end
end

class SynObjectSelection < SyntaxObject
  attr_reader :object, :right
  def_delegators :@object, :syn_name
  attr_writer :right
  
  def initialize(left, right)
    @object = left
    @right = right
  end
  
  def to_parenth_notation
    return "[#{@object.to_parenth_notation} . #{@right.to_parenth_notation}]"
  end
  
  def syn_type
    return @object.sym_table[@right.syn_name].syn_type
  end
  
  def sym_table
    if @right.kind_of?(SynVar)
      pat = Pat.new(:name => "#{@object}.#{@right}", :parent => @object.sym_table)
      @right = @right.to_syn_object(pat)
    end
    @right.sym_table
  end
  
  def to_s
    "#{@object}.#{@right}"
  end
  
  def is_left_value?
    return true
  end
end

class SynReturn < SyntaxObject
  def initialize(expr)
    @expr = expr
  end
  
  def to_parenth_notation
    "(return #{@expr.to_parenth_notation})"
  end
  
  def generate_to_parrot(params)
    str = @expr.generate_to_parrot(params)
    
    l = if @expr.kind_of?(SynVar)
      params[:sym_table][@expr].syn_type
    else
      @expr.syn_type
    end
    l = register_type l
    str.append_with_spaces(params[:offset], "restore $#{l}2\n")
    str.append_with_spaces(params[:offset], ".return($#{l}2)\n")
  end
end